#include "aio.h"
#include <windows.h>
#include <stdio.h>

// Copyright (c) 2011, Jasper Bekkers
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the <organization> nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL JASPER BEKKERS BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

static struct AioFileDescriptor {
	AIO_ALIGN(32) volatile LONG free;
	int flags;
	void *args;
	HANDLE hFile;
} descriptors[MAX_AIO];

static struct AioRequest {
	OVERLAPPED overlapped; // overlapped needs to be the first element
	AIO_ALIGN(32) volatile LONG free;
	Aio *aio;
	AioCallback callback;
} requests[MAX_AIO_REQUESTS];

static HANDLE hPort;
static HANDLE *hThread;
static unsigned int numthreads;

static AIO_ALIGN(32) volatile LONG aioInitialized = 0;

struct ThreadNameInfo {
	DWORD dwType;
	LPCSTR szName;
	DWORD dwThreadID;
	DWORD dwFlags;
};

static const DWORD CALLING_THREAD_ID = -1;
static void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName)
{
	ThreadNameInfo info;
	info.dwType = 0x1000;
	info.szName = szThreadName;
	info.dwThreadID = dwThreadID;
	info.dwFlags = 0;

	__try
	{
		RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info );
	}
	__except(EXCEPTION_CONTINUE_EXECUTION)
	{
	}
}

static DWORD WINAPI aioThreadProcWin32(LPVOID threadid)
{
	char name[100];
	sprintf_s(name, 100, "Async I/O 0x%x", (unsigned int)threadid);
	SetThreadName(CALLING_THREAD_ID, name);

	while(aioInitialized)
	{
		DWORD transferred;
		ULONG_PTR fd; // Not used, but might be nice for the future.
		LPOVERLAPPED overlapped;

		BOOL result = GetQueuedCompletionStatus(hPort, &transferred, &fd, &overlapped, 1);

		if(result == TRUE)
		{
			// We can do this because AioRequests first element is an OVERLAPPED
			// structure and because GetQueuedCompletionStatus returns the same
			// overlapped structure we passed to ReadFile/WriteFile.
			AioRequest *req = (AioRequest*)overlapped;
			unsigned int id = req - requests;
			if(req->callback) 
				req->callback(req->aio, Succeeded, id, (unsigned int)transferred);
			InterlockedCompareExchange(&req->free, 1l, 0l);
		}
	}

	return 0;
}

AioErrorCode aioInit(unsigned int numthreads)
{
	AIO_ASSERT(!aioInitialized, "Already initialized Aio");

	if(aioInitialized) return AioAlreadyInitialized;

	InterlockedCompareExchange(&aioInitialized, 1l, 0l);
	::numthreads = numthreads;

	for(int i = 0; i < MAX_AIO; i++)
	{
		descriptors[i].free = 1;
	}

	for(int i = 0; i < MAX_AIO_REQUESTS; i++)
	{
		requests[i].free = 1;
	}

	hPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, numthreads);

	hThread = new HANDLE[numthreads];
	for(unsigned int i = 0; i < numthreads; i++)
		hThread[i] = CreateThread(NULL, 0, aioThreadProcWin32, (LPVOID)i, 0, NULL);

	return Succeeded;
}

AioErrorCode aioFinish()
{
	InterlockedCompareExchange(&aioInitialized, 0l, 1l);

	delete[] hThread;

	return Succeeded;
}


AioErrorCode aioOpen(const char *path, int flags, int *fd, void *args)
{
	if(!aioInitialized) return AioNotInitialized;

	for(int i = 0; i < MAX_AIO; i++)
	{
		// look for a free fd slot & open the file
		if(InterlockedCompareExchange(&descriptors[i].free, 0l, 1l) == 1l)
		{
			*fd = i;

			DWORD attr = 0;
			DWORD open = 0;

			if(flags & AioRead)  open |= GENERIC_READ,  attr |= OPEN_EXISTING;
			if(flags & AioWrite) open |= GENERIC_WRITE, attr |= CREATE_ALWAYS;

			descriptors[i].flags = flags;
			descriptors[i].args  = args;
			descriptors[i].hFile = CreateFileA(
				path, 
				open, 
				FILE_SHARE_READ, 
				NULL, 
				attr, 
				FILE_FLAG_OVERLAPPED, 
				NULL
			);

			if(!descriptors[i].hFile) return InvalidPath;
			if((flags & AioWrite) && GetLastError() != ERROR_ALREADY_EXISTS)
			{
				// Reset error so it doesn't propagate (and because it isn't
				// an error)
				SetLastError(0);
				return GenericIOError;
			}

			HANDLE result = CreateIoCompletionPort(descriptors[i].hFile, hPort, i, 0);

			if(result == NULL) return GenericIOError;

			return Succeeded;
		}
	}

	return MaxFilesOpened;
}

static AioErrorCode aioFindFreeAioRequestAndInit(Aio *aio, int *id, AioCallback callback)
{
	for(int i = 0; i < MAX_AIO_REQUESTS; i++)
	{
		if(InterlockedCompareExchange(&requests[i].free, 0l, 1l) == 1l)
		{
			memset(&requests[i].overlapped, 0, sizeof(OVERLAPPED));

			requests[i].overlapped.Offset = aio->offset;
			requests[i].aio = aio;
			requests[i].callback = callback;

			*id = i;

			return Succeeded;
		}
	}

	*id = -1;

	return MaxRequestsReached;
}

static AioErrorCode aioRead(Aio *aio, int *id, AioCallback callback)
{
	if(!aioInitialized) return AioNotInitialized;
	if(aio->fd < 0 || aio->fd >= MAX_AIO || descriptors[aio->fd].free) return InvalidArgument;

	AioErrorCode error = aioFindFreeAioRequestAndInit(aio, id, callback);
	if(error != Succeeded) return error;

	BOOL result = ReadFile(descriptors[aio->fd].hFile, aio->buf, aio->size, NULL, &requests[*id].overlapped);

	if(result == TRUE) 
		return Succeeded;

	return GenericIOError;
}

AioErrorCode aioWrite(Aio *aio, int *id, AioCallback callback)
{
	if(!aioInitialized) return AioNotInitialized;
	if(aio->fd < 0 || aio->fd >= MAX_AIO || descriptors[aio->fd].free) return InvalidArgument;

	AioErrorCode error = aioFindFreeAioRequestAndInit(aio, id, callback);
	if(error != Succeeded) return error;

	BOOL result = WriteFile(descriptors[aio->fd].hFile, aio->buf, aio->size, NULL, &requests[*id].overlapped);

	if(result == TRUE) 
		return Succeeded;

	return GenericIOError;
}

AioErrorCode aioClose(int fd)
{
	if(!aioInitialized) return AioNotInitialized;

	InterlockedCompareExchange(&descriptors[fd].free, 1l, 0l);

	return Succeeded;
}
